
package RepositoryHarvester;

/* import declarations */
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Lawrence
 */
public class ResponseHandler
{

    /** The response from the server */
    StringBuffer response;
    /** the harvester configuration object */
    HarvestConfiguration configuration;

    /**
     * Constructor that takes a input stream from a response from a repository server
     * and then creates a document instance out of it.
     * @param is the input stream response from a repository server
     * @param config the harvester configuration object containing the settigns
     * pertinent to this harvest.
     */
    public ResponseHandler(InputStream is, HarvestConfiguration config) throws Exception
    {
        Scanner in = new Scanner(is);
        response = new StringBuffer("");        
        configuration = config;
        while(in.hasNext())
        {
            response.append(in.nextLine() + "\n");
        }
        //System.out.println(response);
    }

    /**
     * Uses regex to seperate out records and then store them in the database.
     * Uses the <code>getRecords</code> method to retrieve the
     * seperated out records.
     */
    public void store() throws Exception
    { // store the response in the database

        ArrayList<Record> results = getRecords(); // get a list of the records in the document
        DatabaseConnection dbc = new DatabaseConnection( configuration );
        //connect to the database and exit with an error code if it could not connect to the database.
        if(!dbc.Connect())
        {
            throw new Exception("Error: Failed to store records in local SQL database!");
        }
        //If available, get the complete size of the repo.
        DisplayListSize(); 
        
        if (results != null)
        {
            for (int i = 0; i < results.size(); i++)
            {
                Record rec = results.get(i);
                dbc.addToBatch(rec);                 
            }
            //execute the batch run to add the records to the database
            dbc.executeBatch();
        }
    }

    /**
     * Decomposes the xml received from the server into its constituent records
     * and then returns them as an <code>ArrayList</code>.
     * @return an array list of {@link Harvester.OAIRecord}s.
     */
    public ArrayList<Record> getRecords() throws Exception
    {        
        ArrayList<Record> result = new ArrayList<Record>();
        
        Pattern p = Pattern.compile("< *record *>.*?< */ *record *>", Pattern.CANON_EQ | Pattern.DOTALL);
        Matcher recordMatch = p.matcher(response);

        //Iterate through records
        while(recordMatch.find())
        {            
            String record = response.substring(recordMatch.start(), recordMatch.end());

            //Find identifier
            Pattern identPattern = Pattern.compile("< *identifier *>[^>]*< */ *identifier *>", Pattern.CANON_EQ | Pattern.DOTALL);
            Matcher identMatch = identPattern.matcher(record);
            if(identMatch.find())
            {
                String identifier = extractTagValue(record.substring(identMatch.start(), identMatch.end()));

                //Now find the records set (if it doesnt have a set we are very upset)
                Pattern setPattern = Pattern.compile("< *setSpec *>[^>]*< */ *setSpec *>", Pattern.CANON_EQ | Pattern.DOTALL);
                Matcher setMatch = setPattern.matcher(record);
                String set = "";
                if(setMatch.find())
                {
                    set = extractTagValue(record.substring(setMatch.start(), setMatch.end()));
                }               

                result.add(toRecord(record, identifier, set));
            }
            else
            {
                System.err.println("Malformed Record encountered - No Identifier! Ignoring and continuing");
            }
        }


        //Check for malformed xml
        Pattern nonXml = Pattern.compile("<\\?xml .*?>");
        Matcher nonXmlMatch = nonXml.matcher(response);
        if(!nonXmlMatch.find())
        {
            System.err.println("A non-xml reply was received from the target host:");
            System.err.println(response);
            throw new Exception("Non-XML response from target host.");
        }

        //Match error case as well
        return result;
    }

     /**
     * Takes an string containing a record, along with the records
     * id string, and returns the record stored in an {@link Harvester.OAIRecord}
     * object.
     * @param record the string record we are to turn into a record object
     * @param id the identification number of the record
     * @return the record stored in a <code>OAIRecord</code> object
     */
    public Record toRecord(String record, String id, String set)
    {        
        String identifier = id; // get the identifier
        StringBuffer subjects = new StringBuffer("");
        String date = "1970-01-01T00:00:00Z";

        Pattern p = Pattern.compile("< *header *status *= *\"deleted\" *>");
        Matcher m = p.matcher(record);
        boolean deleted;
        if(m.find())
        {
            deleted = true;
        }
        else
        {
            deleted = false;
        }
        // get the string representation of a node
        String xml = "";
        if (!deleted)  // get the metadata (xml) if the node is not deleted
        {
            Pattern metadataPattern = Pattern.compile("< *metadata *>.*?< */ *metadata *>", Pattern.CANON_EQ | Pattern.DOTALL);
            Matcher metadataMatcher = metadataPattern.matcher(record);
            if(metadataMatcher.find())
            {
                xml = record.substring(metadataMatcher.start(), metadataMatcher.end());
            }
            else
            {
                System.err.println("Malformed Record encountered - No metadata! Ignoring and continuing");
            }

            //Now extract the creation date (first matched)
            Pattern datePattern = Pattern.compile("< *date *>.*?< */ *date *>", Pattern.CANON_EQ | Pattern.DOTALL);
            Matcher dateMatcher = datePattern.matcher(xml);
            if(dateMatcher.find())
            {
                date = extractTagValue(xml.substring(dateMatcher.start(), dateMatcher.end()));
            }
            else
            {
                Pattern dateDcPattern = Pattern.compile("< *dc:date *>.*?< */ *dc:date *>", Pattern.CANON_EQ | Pattern.DOTALL);
                Matcher dateDcMatcher = dateDcPattern.matcher(xml);
                if(dateDcMatcher.find())
                {
                    date = extractTagValue(xml.substring(dateDcMatcher.start(), dateDcMatcher.end()));
                }
            }

            //now extract the subjects
            Pattern subjectPattern = Pattern.compile("< *dc:subject *>.*?< */ *dc:subject *>", Pattern.CANON_EQ | Pattern.DOTALL);
            Matcher subjectMatcher = subjectPattern.matcher(xml);
            boolean first = true;
            while(subjectMatcher.find())
            {
                if(first)
                {
                    subjects.append(extractTagValue(xml.substring(subjectMatcher.start(), subjectMatcher.end())));
                    first = false;
                }else
                {
                    subjects.append(";" + extractTagValue(xml.substring(subjectMatcher.start(), subjectMatcher.end())));
                }
            }

        }

        //Check to see if date format is in yyyy-mm-ddThh:mm:ssZ format
        if(!date.equals("1970-01-01T00:00:00Z"))
        {
            if(date.length() == 20)
            {
                if(date.charAt(10) == 'T' && date.charAt(19) == 'Z')
                {
                    date = date.substring(0, 10);
                }
            }
        }

        //Remove sql breaking things from subjects
        StringBuffer formattedSubjects = new StringBuffer("");
        for(int i = 0; i < subjects.length();i++)
        {
            if(subjects.charAt(i) != 39)
            {
                formattedSubjects.append(subjects.charAt(i));
            }
        }


        Record result = new Record(identifier, set, date, formattedSubjects.toString()); // create a OAI record instance
        return result;
    }

    /**
     * Takes a tag like <resmptionToken>blahblah</resumptionToken>
     * and returns the blahblah part between tags.
     * @param tag the tag we remove the data from
     */
    public String extractTagValue(String tag)
    {
        Pattern p = Pattern.compile(">.*<", Pattern.CANON_EQ | Pattern.DOTALL);
        Matcher m = p.matcher(tag);
        m.find();
        return tag.substring(m.start()+1, m.end()-1);
    }


    /**
     * Attempts to display the complete list size,
     * so that one can track the progress of the harvest. 
     */
    public void DisplayListSize()
    {
        //<resumptionToken completeListSize='819' cursor='0'>1950426</resumptionToken>
        Pattern p = Pattern.compile("< *resumptionToken.*?>[^>]*< */ *resumptionToken *>", Pattern.CANON_EQ | Pattern.DOTALL);
        Matcher m = p.matcher(response);
        if(m.find()) // If there is a resumptionToken
        {
            //Find the cursor
            String cursorMatch = response.substring(m.start(), m.end());
            Pattern cursorList = Pattern.compile("cursor='[0-9]+'", Pattern.CANON_EQ);
            Matcher cursorListMatch = cursorList.matcher(cursorMatch);
            if(cursorListMatch.find()) //If there is a completeListSize
            {
                String listSizeString = cursorMatch.substring(cursorListMatch.start(), cursorListMatch.end());
                Pattern digit = Pattern.compile("[0-9]+", Pattern.CANON_EQ);
                Matcher digitMatch = digit.matcher(listSizeString);
                digitMatch.find();

                int cursorSize = Integer.parseInt(listSizeString.substring(digitMatch.start(), digitMatch.end()));
                System.out.print("Processing records at "+cursorSize);

            }

            //Find the complete list size
            String match = response.substring(m.start(), m.end());
            Pattern list = Pattern.compile("completeListSize='[0-9]+'", Pattern.CANON_EQ);
            Matcher listMatch = list.matcher(match);
            if(listMatch.find()) //If there is a completeListSize
            {
                String listSizeString = match.substring(listMatch.start(), listMatch.end());
                Pattern digit = Pattern.compile("[0-9]+", Pattern.CANON_EQ);
                Matcher digitMatch = digit.matcher(listSizeString);
                digitMatch.find();

                int listSize = Integer.parseInt(listSizeString.substring(digitMatch.start(), digitMatch.end()));
                System.out.print("/"+listSize);
            }

            System.out.print("\n");
        }
        else
        {
            System.out.println("No list size available");
        }
    }

    /**
     * Gets the resumption token out of the xml response received from the repository
     * server.
     * @return the resumption token.
     */
    public String getResumptionToken()
    { // get the resumption token from the repsonse
        Pattern p = Pattern.compile("< *resumptionToken.*?>[^>]*< */ *resumptionToken *>", Pattern.CANON_EQ | Pattern.DOTALL);
        Matcher m = p.matcher(response);

        if(m.find())
        {
            String longResump = response.substring(m.start(), m.end());
            return extractTagValue(longResump);
        }
        else
        {
            return "";
        }
    }   

}
